package br.cin.ufpe.corba;

import java.io.IOException;
import java.io.Serializable;

import br.cin.ufpe.corba.iiop.ObjectReplyBody;
import br.cin.ufpe.corba.iiop.Reply;
import br.cin.ufpe.corba.iiop.ReplyStatusType;
import br.cin.ufpe.corba.iiop.Request;
import br.cin.ufpe.corba.iiop.SystemExceptionReplyBody;
import br.cin.ufpe.corba.interfacerepository.InterfaceDef;

public class ObjectRef implements Serializable {
	
	private static final long serialVersionUID = 5853135870656242199L;
	
	protected transient ORB orb;	
	protected InterfaceDef interfaceDef;	
	protected ObjectAddress objectAddress;
	
	public ObjectRef(ORB orb) {
		this.orb = orb;
	}
	
	public InterfaceDef get_interface() {
		return interfaceDef;
	}
	
	public void set_interface(InterfaceDef interfaceDef) {
		this.interfaceDef = interfaceDef;
	}
	
	public boolean is_nil() {
		return false;
	}
	
	public ObjectRef duplicate() {
		return (ObjectRef) clone();
	}
	
	public void release() {
		// Faz nada
	}
	
	public boolean is_a(String repository_id) {
		return interfaceDef.is_a(repository_id);
	}
	
	public boolean is_a(Class<?> repository_id) {
		return interfaceDef.is_a(repository_id.getCanonicalName());
	}
	
	public boolean non_existent() {
		boolean result = false;
		
		try {
			result = (Boolean) invoke("non_existent", null, null, true);
		}
		catch (Exception e) {
			result = true;
		}
		
		return result;
	}
	
	public boolean is_equivalent(ObjectRef other_objectref) {
		return this.objectAddress.equals(other_objectref.objectAddress);
	}	
	
	public long hash(long maximum) {
		long result = objectAddress.hashCode() % maximum; 
		return  result < 0 ? -result : result;
	}
	
	Request create_request(Context ctx, String operation, String[] argumentTypes, Object[] arguments, boolean hasReturn) {
		return new Request(this.objectAddress.getKey(), operation, arguments, argumentTypes, hasReturn);
	}
	
	public String repository_id() {
		return interfaceDef.getRepositoryId();
	}
	
	public ORB get_orb() {
		return orb;
	}
	
	public ObjectAddress getObjectAddress() {
		return objectAddress;
	}

	public void setObjectAddress(ObjectAddress objectAddress) {
		this.objectAddress = objectAddress;
	}
		
	public Object invoke(String method, String[] argumentTypes, Object[] arguments, boolean hasReturn) {
		
		// Guarantees that just Object references will be transmitted
		for (int i = 0; i < arguments.length; i++) {
			if (arguments[i] instanceof Servant) {
				arguments[i] = ((Servant)arguments[i]).duplicate();
			}
		}
		
		Request request = create_request(null, method, argumentTypes, arguments, hasReturn);		
		
		try {
			Reply reply = orb.invoke(objectAddress, request);
						
			// Handle exceptions
			if (reply.getHeader().getReplyStatus() != ReplyStatusType.NO_EXCEPTION) {
				SystemExceptionReplyBody ex = (SystemExceptionReplyBody) reply.getBody();
				throw ex.getException();
			}
			
			if (hasReturn == false) {
				return null;
			}
			
			// Return the result
			ObjectReplyBody body = (ObjectReplyBody) reply.getBody();
			Object result = body.getResult();
			
			if (result instanceof RuntimeException) {
				throw ((RuntimeException) result);
			}
			
			if (result instanceof Exception) {
				throw new RuntimeException((Exception) result);
			}
			
			if (result instanceof ObjectRef) {
				((ObjectRef) result).orb = orb;
			}
			
			return result;
		} catch (IOException e) {
			throw new RuntimeException(e);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		}
	}
	
	@Override
	protected Object clone() {
		ObjectRef newRef = new ObjectRef(orb);
		newRef.set_interface(this.interfaceDef);
		newRef.setObjectAddress(this.objectAddress);
		return newRef;
	}
}
